May 8th 2023
Contents
HTTP/1
Encryption and hashing
인터넷을 이용하는데 있어 보안은 매우 중요해요. 이 포스팅을 통해서는 클라이언트와 서버 간 통신 시 데이터를 암호화 하는 방법에 대해 알아보려해요. 웹 사이트의 HTTPS 설정 방법과 SSL / TLS Certificate에 대해 알아보겠습니다.
아래 자료는 2018년도 기준이긴 하지만 참고해보면, 전 세계의 가장 많이 사용되고 있는 웹사이트 중 50% 이상이 Https를 사용하고 있는것으로 나타납니다.
아래 자료를 더 살펴보면 2021년 8월 기준 많이 이용되고 있는 전 세계 사이트 중 약 14% 정도만 이 A+ 등급의 보안 등급을 가진다고 합니다.
참고: https://www.ssllabs.com/ssl-pulse/
크롬 브라우저에서 개발자툴을 열고 http로 된 웹사이트와 https로 된 웹사이트를 접속해 봅니다.
두 웹사이트의 접속 시 헤더를 살펴보면 http 사이트는 포트 80을, https로 된 사이트는 443 포트로 접속한 것을 확인할 수 있습니다.
접속한 ip를 와이어샤크에서 필터링 후 해당 서버와의 커뮤니케이션 시 패킷을 확인해 볼 수 있습니다. (ip주소는 크롬 개발자툴이나 터미널에서 nslookup 명령어로 확인해 볼 수 있어요.)
웹은 인터넷의 일부 비중을 차지하고 있고, 많은 사람들이 자신이 웹을 사용하고 있다는 것을 잘 인지하지 못 하고 사용하는 영역이기도 합니다. 앱을 다운로드 하거나, 페이스북에서 메세지를 주고 받을 때 우리는 웹을 사용합니다. 좀 더 구체적으로, 서버와 클라이언트가 통신을 합니다.
그러면 서버와 클라이언트가 통신을 한다는 것이 무슨 의미일까요? 웹사이트에서 다른 페이지로 이동을 하면 어떤 동작이 일어날까요? 모바일 폰은 어떻게 메세지가 왔다는 것을 알 수 있을까요? 그리고 웹의 한계점은 무엇일까요? 더 나은 개발자가 되기 위해, 브라우저에서 HTTP를 구현하는 것 뿐만 아니라 그 근간을 이해하는 것 또한 중요한 것 같습니다.
인터넷은 웹 이전에도 존재 했으며, email, FTP와 같은 방식으로 인터넷을 이용해 통신을 했어요. 하지만, 공개적으로 문서를 게재하고 접근할 수 있는 방법은 없었습니다. 이에 Tim Berners-Lee는 인터넷 상 문서를 연결시켜 Hypertext documents라고 부르고, SGML의 일부를 이용해 HTML(HyperText Markup Language)라고 불렀습니다. 그리고, HTML 문서를 주고 받기 위해 HTTP (Hypertext Transfer Protocol)을 만들었습니다.
Hypertext라는 용어가 어렵게 느껴질 수도 있는데, 문서 내 텍스트가 다른 문서를 참조할 수 있는 것을 의미한다고 해요. 이러한 참조를 우리가 알고 있는 Link라고 부릅니다. Hypertext 는 문서 뿐만 아니라 이미지, 스타일, 영상 등 어떤것도 참조할 수 있습니다.
HTTP는 어떻게 동작할까요?
팀 버너스리는 서버에 document 요청을 보내기 위해 템플릿을 고안했어요. 클라이언트에서 템플릿 양식에 맞게 필요한 내용을 기입하고 서버에 전달해요.
HTTP를 아는 서버는 클라이언트가 보낸 요청을 이해하고 이에 대한 응답을 보내게 되요. HTTP는 응답에 대한 템플릿도 있으며, 한 요청에는 정상적인 응답, 에러, 리라우팅 등 다양한 경우의 응답이 존재합니다.
GET /pictures/ironman.jpg HTTP/1.1
Host: play.watcha.net
User-Agent: Mozilla/5.0
Connection: keep-alive
Accept: text/html
If-None-Match: b1arb2f45f
위 Http 요청 중 GET과 같이 Http Method가 정의된 부분 밑으로 Header에 속하며, 해당 요청에 대한 추가 정보가 담겨 있어요. 일반적으로 브라우저 타입, 지원 포맷, 브라우저 로컬 캐시의 문서 버전 등이 담겨 있습니다. Host Header만 제외하고는 모두 optional 이에요. 때문에, 요청을 위해 최소 Http Method와 Host 정보가 필요합니다.
HTTP/1.1 200 OK
Content-Length: 12345
Server: Apache
Content-Type: text/html
Date: Wed, 26 May 2020
Etag: ab23e3847
<binary data>
Http Response의 포맷 중 중요한 부분은 첫 번째 줄로, 요청에 대한 응답 상태가 담겨 있어요. 위 내용은 Response Header로 문서에 대한 정보 뿐만 아니라, 서버와 Connection 등의 정보도 담겨 있어요. Request 와 마찬가지로 대부분의 헤더는 Optional이고 Content-Length는 꼭 필요해요. 이 정보를 통해 클라이언트가 데이터의 크기를 미리 알 수 있게 됩니다. 헤더 이후 한 칸의 빈 줄 이후에 실제 Document 가 전달되며, 문서 뿐만 아니라 이미지, JSON 등이 전달될 수 있어요.
일반적으로 서버가 응답으로 index.html을 전달하면, 브라우저가 Response를 파싱 후 index.html 파일을 읽고 필요한 스타일시트나 이미지 등의 리소스를 추가 요청합니다.
저는 지금 Watcha에서 프론트엔드 개발을 하고 있습니다. 매일 HTTP를 사용하고 있지만, 단순히 사용하기 보다는 왜 이 기술을 사용하는지 또는 왜 사용하지 않는지에 대해서도 이해를 해야 더 나은 개발자가 될 수 있다 생각해요.
GET, POST, PUT, DELETE와 같은 HTTP Method를 많이 사용 하지만, 알아두면 유용한 다른 메소드도 있습니다. HEAD의 경우 전체 파일을 제외하고 헤더만 확인할 수 있는 메소드입니다.
HEAD 메소드는 Response의 사이즈를 확인하거나, 페이지의 Cached Version이 아직 최신 버전인지 확인을 해 불피요한 다운로드를 방지할 수 있어요. 하지만, 다시 페이지를 받아야 하는 경우 GET 요청을 보내야 하는데, 이렇게 되면 클라이언트 서버와의 통신 횟수가(round-trip) 증가하고 이는 응답을 받는데 드는 시간을 많이 증가시키게 됩니다.
또다른 메소드로 OPTIONS이 있습니다.
OPTIONS는 서버에서 허용하는 HTTP METHOD를 확인할 수 있는데, 모든 서버가 이를 지원하지는 않습니다.
일반적으로 중요한 헤더는 아래와 같은 항목이 있어요.
Content-Length는 모든 Response에 존재하는 항목이고, 브라우저에게 Body의 사이즈를 알려줍니다. 이를 통해 브라우저는 얼마나 큰 Bytes를 받아야 하는지 알 수 있고 파일 다운로드 시 프로그레스 바를 보여줄 수 있어요.
Content-Type 또한 필수적인 항목이에요. 브라우저는 Content-Type을 통해 이미지면 이미지를 보여주고, text/html이면 파싱 후 필요 시 추가적으로 필요한 HTTP 요청을 보내게 됩니다.
문서가 수정된 날짜에요. 하지만, 개발자가 일부만 수정하고 전체 파일을 업로드 하게 되면, 이 날짜를 신뢰할 수 없게 돼요. 그래서 대부분의 서버는 ETag라는 것을 보냅니다. Entity Tag의 축약형으로 파일의 콘텐트가 변경 시에만 수정이 되며, 주로 SHA256 hash function이 ETag 생성을 위해 사용됩니다.
Cache-Control은 이름이 시사하는 바와 같이, 서버에서 클라이언트가 얼마나 오랜 기간 Response를 캐시하게 할 지 컨트롤 할 수 있도록 합니다. Cache-Control은 매우 복잡하며 많은 기능이 있으며, 기본적으로 "cacheability"와 "max-age"가 사용되어요.
서버에서 이 항목의 날짜를 체크하고 필요하지 않다면 데이터를 전송하지 않을 수 있어요. 위 ETag와도 연관이 있는데, "If-Modified-Match"라는 헤더가 있어요. 문서의 ETag와 "If-Modified-Match" 헤더의 ETag 와 같은 경우, 서버는 실제 데이터를 전송할 필요가 없게 되어요. "If-Modified-Match"와 "If-Modified-Since"가 한 헤더에 존재할 수 있는데, ETag가 더 정확하기 때문에 "If-Modified-Since"보다 우선시 됩니다.
앞서 간단하게 netcat을 이용해 HTTP 요청을 확인해 보았어요. 하지만, 아직 우리가 확인하지 못 한 부분이 있는데, 이 부분이 요청을 보내고 응답을 받는 속도에 영향을 줄 수 있습니다.
HTTP와 같은 Application Layer는 바로 밑 층인 TCP Layer에 의존적이에요. TCP는 두 컴퓨터 간 여러 독립적인 데이터 스트림을 가질 수 있도록 해 주고, 이 스트림은 Port로 관리가 되어요.
TCP 연결을 위해 두 컴퓨터는 처음 TCP Handshake를 하는데, 이는 두 컴퓨터가 새로운 연결이 성립되었다는 것을 인지하도록 합니다. 여기에서 한 번 round-trip이 발생해요. 만약 HTTPS를 사용한다면, TLS Handshake 를 위해 또 한번의 round-trip이 생겨요. 만약 이러한 과정에서 Head-of-Line Blocking이 발생하면, 웹 성능에 큰 영향을 줄 수 있어요.
브라우저에서 주소창에 URL 입력 후 화면이 나타나는데 까지 걸리는 시간의 50%는 서버로부터의 응답을 기다리는데 소요된다고 해요. 브라우저의 개발자 툴의 네트워크 탭에서 TTFB(Time to First Byte)를 통해 이 시간을 확인을 할 수 있어요.
일반적으로 웹사이트에서 추가 자원을 요청하게 되는데, 이는 즉 추가 자원을 요청하려면 첫 요청에 대한 응답을 기다리며 의미 없는 시간을 보내야 하는 것을 의미해요. 이것이 길어지는 경우 Head-of-Line Blocking을 겪게 됩니다.
HTTP 요청은 Queue와 같아서 앞선 요청이 처리되는 동안 금방 끝날 수 있는 다음 요청들이 있어도 앞 작업이 마치기를 기다려해 해요. 이에 대응하기 위해 브라우저에서 6개의 요청까지 병렬로 처리하지만, 만족스럽지는 않아요.
브라우저가 서버에 요청을 보내면 TCP Handshake를 거쳐야 해요. 시간이 소요되는 작업인데 이를 수월하게 하기 위해 HTTP / 1.1에 Keep-Alive라는 개념이 도입되었어요. Keep-Alive header 가 설정되면 서버는 요청에 대한 응답을 보내고 connection을 종료하지 않습니다. 이를 클라이언트는 추가 자원 요청을 위해 재사용 할 수 있어, 허용 가능한 6개의 connection을 좀 더 효율적으로 사용할 수 있습니다. 자바스크립트나 css 가 웹팩을 이용해 한 파일로 번들링 되는 이유 중 하나 이기도 해요.
공용 장소에서 와이파이를 이용할 때 보안성에 대해 생각해 본 적 있나요? 저는 그다지 없네요. 하지만, 공용 와이파이를 악용하면 누군가 내 트래픽을 확인할 수 있고 주고 받는 데이터를 조작할 수 있다고 해요. 이러한 위험을 줄이기 위해 HTTPS를 사용하게 되었어요.
유저가 원하는 서버에 연결되었다고 생각하지만, 그렇지 못 한 경우도 존재할 수 있어요. MITM(Man In The Middle) attack이라고도 하는데, 서버와 연결 시 중간에서 데이터를 염탐하는 행위입니다. 그리고, HTTPS 는 이에 대한 대응으로 인증이라는 다른 기능이 있습니다.
HTTPS는 HTTP와 TLS라는 두 가지 개념을 포함하고 있어요. TLS는 Transport Layer Security의 약자로 HTTP에만 종속된 개념이 아니고 어떤 프로토콜에서도 사용이 가능해요. 예로, FTP와 FTPS가 있습니다.
TLS는 데이터를 의도된 수신자만 확인 가능하도록 암호화 합니다. 의도한 서버와의 통신을 확실히 하기 위해 TLS는 chain of trust 라는 개념을 사용합니다. 서버는 자신에 대한 메타데이터와 암호화 키를 포함한 인증서를 인증 기관을 통해 발행합니다. 인증 기관을 통해 서명된 인증서를 통해 자신이 사용하는 암호화 키가 올바른지 확인할 수 있고 이를 통해 원하는 서버와 통신하고 있는지 확인할 수 있습니다.
비밀번호 암호화 과정에서 사용되기도 하는 공개 키 암호화방식을 TLS에서도 사용합니다. 일반적으로 메세지를 암호화 하는데 공개키를 사용해 누구나 메세지는 전달할 수 있도록 하고, 비밀키를 가진 곳에서만 메세지를 복호화 할 수 있게 합니다.
제가 접속한 ip 주소로 필터링 해보면 많은 패킷 내역을 확인할 수 있습니다. 상단 몇 개의 아이템을 살펴보면 TCP 프로토콜과 TLS 프로토콜을 볼 수 있습니다.
패킷 리스트 하단에 보면 해당 패킷의 여러 헤더를 볼 수 있습니다. 위 사진에서는 Frame 헤더, Internet Protocol 헤더, Transmission Control Protocol 헤더 등을 볼 수 있습니다.
TCP 프로토콜 헤더 세부사항을 확인해보면, Source Port는 1024보다 큰 수의 포트가 할당되고, Destination Port는 Https 포트인 443인 것을 확인할 수 있습니다.
그리고 Sync Flag로 새로운 TCP 커넥션을 여는 패킷인 것을 시작으로, 상단 세 개의 패킷을 통해 TCP Three way handshake 작업이 진행된 것도 확인해 볼 수 있습니다.
인터넷 프로토콜 쪽을 살펴보면, IP 주소와 TTL 등을 볼 수 있고, 다음 헤더가 TCP 헤더인 것을 가리키는 것도 확인해 볼 수 있습니다.
그 외에 MAC Address 등 여러 정보가 담겨 있습니다.
데이터 암호화 방식은 과거 사용했던 방식의 DES, 3DES 방식과 AES, RSA와 같이 현재 사용하고 있는 방식을 예로 들 수 있습니다. 이 중 RSA는 비대칭암호화방식(Asymmetric Encryption)이며, 나머지는 대칭암호화(Symmetric Encryption) 방식입니다.
대칭암호화 방식은, 데이터가 암호화 되는 곳과 복호화하는 곳에서 같은 키를 사용해 처리하는 방식입니다. 키를 가지고 있으면 데이터를 암호화 복호화 할 수 있기 때문에 매우 조심히 다루어야 합니다.
대칭키 방식의 단점은 암호화된 데이터를 주고 받는 곳에 대칭키를 전달해야 하는데, 이 전달하는 과정에서 대칭키가 유출될 수 있는 위험이 있다.
비대칭암호화방식은 대칭키 방식과는 다르게 Private Key, Public Key 두 개의 다른 키를 이용해 데이터를 복호화, 암호화 하는 방식입니다.
비대칭암호화방식에서 Public Key를 전달 받은 클라이언트는 공개된 키로 데이터를 암호화 하고, 암호화된 데이터를 서버에 전달합니다. 서버에서는 Private Key는 안전한 장소에 보관하며 전달 받은 데이터를 복호화 하는데 사용할 수 있습니다.
Private, Public 키 방식은 데이터 서명에도 사용합니다. 개인키 "전자서명", 공개키 "서명 검증"으로 불리기도 합니다. 인지해야 할 점은 서명을 위해 이 방식을 사용할 때 데이터의 흐름이 위의 데이터를 암호화 할 때와 다르다는 것입니다. Private Key는 디지털서명을 생성하는데 사용하고, Public Key는 디지털서명을 검증하는데 사용합니다.
이 경우에는 개인키로 암호화하고, 공개키로 암호화된 데이터를 복호화합니다. Private Key를 소유한 서버에서 데이터와 데이터를 해시한 후 암호화 한 값을 함께 클라이언트에게 전달합니다. 이 값을 전달 받은 클라이언트는 데이터를 송신한 측의 신원을 확인하기 위해 Public Key를 사용합니다. 암호화된 해시를 복호화하고, 이를 전달 받은 데이터를 해시한 값과 비교하여 일치하는지 확인합니다.
이러한 과정이 서버와 클라이언트가 통신할 때 이루어집니다. 이를 통해 데이터가 이동 중 변경되지 않은 것을 확인할 수 있고, 데이터가 통신하려는 상대방으로부터 전달 받은 것인지 검증할 수 있습니다.
실제 터미널에서 RSA 방식으로 공개키, 비공개키를 생성하고 이를 이용해 데이터를 암호화 복호화 하는 연습을 해 볼 수 있습니다.
우선 아래와 같은 방식으로 private key를 생성할 수 있습니다.
openssl genrsa -aes256 -out private.pem
위 명령어를 입력하면 aes256로 private key를 암호환 한 후 private.pem 파일을 생성합니다.
생성한 private key를 확인해 보면 aes256 알고리즘으로 암호화 된 것을 확인할 수 있습니다.
private key를 생성했고, 생성한 private key에서 public key를 추출할 수 있습니다.
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
RSA라는 이름은 세 개발자의 이름에서 유례하고, 널리 사용되고 있는 비대칭 암호화 알고리즘입니다. 이 알고리즘은 리만가설에 근거하고 큰 수를 소인수분해하는 것은 어렵다는 것에 기반을 두고 있다고 합니다.
RSA는 비대칭키를 이용해 안전한 데이터 관리와 전자 서명 등의 목적으로 현재 네트워크 상 주요하게 사용되고 있습니다. RSA 암호화 방식에서는 Public, Private 두 개의 키를 사용해 데이터를 암호화, 복호화 합니다. 두 개의 키 length는 같으며 보통 2024 length가 많이 사용됩니다. 두 개의 키는 세트로 작동하기 때문에 만약 둘 중 하나의 키가 오용될 가능성이 있다면 Public, Private 세트로 키를 재생성 해야합니다.
Public Key Infrastructure(PKI)는 공개 키 암호화 방식에 사용되는 해쉬(Cryptographic Hash), 비대칭키 등과 같은 여러 구성요소와 절차 등을 의미할 때 사용됩니다. PKI의 주요 구성요소를 살펴보면 아래와 같습니다.
PKI를 적절히 활용하면 보안적인 면에서 아래와 같이, 여러 기능을 구현할 수 있습니다.
이렇게 PKI는 공개키 암호화와 전자서명의 기반이 됩니다. 그리고, 이렇게 데이터를 암호화하고 인증하는 작업은 인증서(Certificate)를 기반으로 작동합니다.
인증서는 SSL 암호화를 통해 웹사이트를 보호하기위해 사용되기도 하고, VPN 안에서 안전하게 데이터를 주고 받기 위해 사용되기도 합니다.
HTTPS를 지원하는 서버를 웹브라우저에서 접근하면 아래와 같이 인증서 정보를 볼 수 있습니다.
인증서는 Digital Entity입니다. 즉, 데이터를 포함한 파일입니다. 인증서에는 아래와 같은 데이터를 포함하고 있습니다.
실제로 웹브라우저에서도 해당 정보를 확인해 볼 수 있습니다.
인증서에 포함된 서명은 비밀키를 이용해 암호화 됩니다. 만약 서명이 CA나 Intermediate Certificate Authority에 의해 생성되었다면, chain of trust를 형성해 인증서와 인증서의 소유권에 대해 신뢰할 수 있습니다.
인증서의 메카니즘이 복잡하지만, 핵심적인 기능은 아래 두 가지로 요약해 볼 수 있습니다.
인증서 관련 중요하게 알고 있어야 하는 점은, 모든 인증서는 소유권자의 공개키가 포함 돼 있다는 점입니다. 인증서를 이용하는 근복적인 목적 자체가 이 공개키에 있습니다.
How web browser trust ROOT CA? How chain of trust is built? When certificate is invalid?
Hash는 알고리즘에 따라 128비트, 256비트 또는 그 이상 길이로 구성된 문자열입니다. Input Data가 Hash Function에 주어지면 해시 함수는 알고리즘에 따라 정해진 길이의 문자열을 반환합니다.
그리고 반환하는 문자열은 이전의 상태로 되돌릴 수 없습니다. 즉, 한 번 해시된 결과를 받으면 이전의 데이터가 무엇인지 확인할 수 없습니다. 해시의 또다른 특성은 Input이 조금만 변경 되어도 결과값은 완전히 다른 값을 반환한다는 점입니다. 이를 이용해 데이터 수신측에서 전달 받은 데이터를 기반으로한 해시와 기존의 해시를 비교해 데이터의 정합성 확인을 할 수 있습니다.
encrypt traffic HTTP and HTTPS TCP Certificates